#  File src/library/methods/R/methodsTable.R
#  Part of the R package, https://www.R-project.org
#
#  Copyright (C) 1995-2024 The R Core Team
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  A copy of the GNU General Public License is available at
#  https://www.R-project.org/Licenses/

### merge version called from namespace imports code.  Hope to avoid using generic
.mergeMethodsTable2 <- function(table, newtable, envir, metaname) {
    old <- as.list(table, all.names=TRUE)
    mm <- 1
    for( what in old) {
      if(is(what, "MethodDefinition")) {
          mm <- length(what@defined)
          break
      }
    }
    new <- as.list(newtable, all.names=TRUE)
    ## check that signature length doesn't change
    canStore <- TRUE
    for(what in new) {
        if(is(what, "MethodDefinition") &&
           length(what@defined) != mm) {
            canStore <- FALSE
            break
        }
    }
    if(canStore) {
        list2env(new, table)
    }
    else { # rats! have to get the generic function
        f       <- gsub("^\\.__T__(.*):([^:]+)",     "\\1", metaname)
        package <- gsub("^\\.__T__(.*):([^:]+(.*))", "\\2", metaname)
        generic <- getGeneric(f, TRUE, envir, package)
        .mergeMethodsTable(generic, table, newtable, TRUE)
    }
    table
}

## action on attach, detach to merge methods tables
.mergeMethodsTable <- function(generic, table, newtable, add = TRUE) {
  fenv <- environment(generic)
##  signature <- generic@signature
  if(!exists(".SigLength", envir = fenv, inherits = FALSE))
     .setupMethodsTables(generic)
  allTable <- if(!add) get(".AllMTable", envir = fenv) ## else NULL
                                        # .AllMTable but only if required
  n <- get(".SigLength", envir = fenv)
  anySig <- rep("ANY", n) # assert doesn't need to be a real signature
##  anyLabel <- .sigLabel(anySig)
  newMethods <- names(newtable)
  for(what in newMethods) {
    obj <- newtable[[what]]
    if(is.primitive(obj))
      sig <- anySig
    else if(is(obj, "MethodDefinition"))
      sig <- obj@defined
    else if(is.environment(obj)) {
       objsWhat <- as.list(obj, all.names=TRUE, sorted=TRUE)
       if(length(objsWhat) == 0)
           next # empty environment, ignore
       isDef <- vapply(objsWhat, is, logical(1L), "MethodDefinition")
       if (any(isDef)) {
           obj <- objsWhat[[utils::tail(which(isDef), 1L)]]
           sig <- obj@defined
       } else {
           sig <- anySig
       }
    }
    else
      stop(gettextf("invalid object in meta table of methods for %s, label %s, had class %s",
                    sQuote(generic@generic),
                    sQuote(what),
                    dQuote(class(obj))),
           domain = NA)
    ns <- length(sig)
    if(ns == n) {}
    else {
      if(ns < n) {
        nadd <- n - ns
        sigPackage <- packageSlot(sig)
        if(length(sigPackage)< ns) # probably out of date?
            sigPackage <- c(sigPackage, rep("", ns - length(sigPackage)))
        sig <-  .simpleSignature(c(sig, rep("ANY", nadd)),
                    names = generic@signature[1:n],
                    packages = c(sigPackage, rep("methods", nadd)))
        obj <- .xpdSignature(obj, sig, n-ns)
        what <- .sigLabel(sig)
        ns <- n
      }
      else if(add) { # and ns > n
        signames <- generic@signature
        length(signames) <- ns
        .resetTable(table, ns, signames)
        fenv[[".SigLength"]] <- ns
        n <- ns
      }
    }
    if(add) {
        if(exists(what, envir = table, inherits = FALSE)) {
            obj <- .newOrMultipleMethod(obj, what, table)
            ## must replace in .AllMTable also
            if(is.null(allTable))
                allTable <- get(".AllMTable", envir = fenv)
            allTable[[what]] <- obj
        }
        table[[what]] <- obj
    }
    else if(exists(what, envir = table, inherits = FALSE) &&
            !all(obj@defined == "ANY") ) {
        ## remove methods, but not the default
        remove(list = what, envir = allTable)
        remove(list = what, envir = table)
    }
    ## else warning?
  }
  NULL
}

.xpdSignature <- function(obj, sig, nadd) {
    if(is(obj, "MethodDefinition")) {
        obj@defined <- sig
        obj@target <- sig
    }
    else if(is.environment(obj)) {
##        xtrPkg <- rep("methods", nadd)
        for(what in names(obj)) {
            objw <- get(what, envir = obj)
            if(is(objw, "MethodDefinition")) {
                sigw <- objw@defined
                pkgw <- packageSlot(sigw)
                if(length(pkgw) < length(sigw))
                    pkgw <- c(pkgw, rep("", length(sigw) - length(pkgw)))
                sigw <- .simpleSignature( c(sigw, rep("ANY", nadd)),
                        names = names(sig),
                        packages = c(pkgw, rep("methods", nadd)))
                objw@defined <- objw@target <- sigw
                remove(list = what, envir = obj)
                var <- .pkgMethodLabel(objw)
                if(nzchar(var)) obj[[var]] <- objw
            }
        }
    }
    obj
}

## a simpler version of setting up a signature object
## For better or worse, the initialize() method expects
## a function definition and calls .MakeSignature()
.simpleSignature <- function(classes, names, packages) {
    object <- new("signature")
    object@.Data <- classes
    object@names <- names
    object@package <- packages
    object
}

.newOrMultipleMethod <- function(obj, what, table) {
    if(!.duplicateClassesExist())
        return(obj)
    current <- get(what, envir = table)
    if(is.environment(current)) {
        if(is.environment(obj))
            list2env(as.list(obj, all.names=TRUE), current)
        else if(is(obj, "MethodDefinition")) {
            var <- .pkgMethodLabel(obj)
            if(nzchar(var)) assign(var, obj, envir = current)
        }
        current
    }
    else if(is(current, "MethodDefinition")) {
        curPkg <- packageSlot(current@defined)
        if(is(obj, "MethodDefinition")) {
            objPkg <- packageSlot(obj@defined)
            if(is.null(curPkg) || is.null(objPkg) ||
               identical(curPkg, objPkg))
                return(obj)
            else {
                merge <- new.env()
                var <- .pkgMethodLabel(obj)
                if(nzchar(var)) assign(var, obj, envir = merge)
                var <- .pkgMethodLabel(current)
                if(nzchar(var)) assign(var, current, envir = merge)
                return(merge)
            }
        }
        else if(is.environment(obj)) {
            merge <- new.env()
            assign(.pkgMethodLabel(current), current, envir = merge)
            list2env(as.list(obj, all.names=TRUE), merge)
            return(merge)
        }
        ## else adding a primitive, should do nothing
        else
            current
    }
}


## called only from  .setupMethodsTables()  [further below]:
.mtableAddToTable <- function(generic, mlist, table = new.env(TRUE, fenv), add = TRUE) {
  fenv <- environment(generic)
  signature <- generic@signature
  if(!exists(".SigLength", envir = fenv, inherits = FALSE))
     .setupMethodsTables(generic)
  n <- get(".SigLength", envir = fenv, inherits = FALSE)
  .storeMtable(table, rep("ANY", n), mlist, 1,  add, fenv)
  ## check for more args in the mlist than in the table
  nNow <- get(".SigLength", envir = fenv, inherits = FALSE)
  if(nNow > n) {
    length(signature) <- nNow
    .resetTable(table, nNow, signature)
  }
  table
}

## utility now *only* called once above
.storeMtable <- function(table, sig, method, i, add, fenv) {
    ## method: a method or NULL.
    if(is.null(method)) return(table)
    ## else
    m <- list(ANY=method)

  ## the methods slot is a list named by class, with elements method definitions
  classes <- names(m)
  for(j in seq_along(m)) {
    el <- m[[j]]
    sig[[i]] <- classes[[j]]
    if(is(el, "MethodDefinition") || is.primitive(el)) {
      if(add)
        assign(.sigLabel(sig), el, envir = table)
      else
        remove(list = .sigLabel(sig), envir = table)
    }
    else if(is(el,"MethodsList")) {
	.MlistDefunct()
    }
    else
      stop(gettextf(
   "invalid method element for signature %s at level %d (should be MethodDefinition or .Primitive, had class %s)",
                    sQuote(classes[[j]]),
                    i,
                    dQuote(class(el))),
           domain = NA)
  }
  table
}

.cacheMethodInTable <- function(fdef, sig, def,
                                table = get(".AllMTable", envir = fenv)) {
    ## store method in cache table.
    ## called from setMethod(), and 
    ## also   from cacheMethod (from as(), as<-())
  fenv <- environment(fdef)
  if(missing(table) && !exists(".AllMTable", envir = fenv, inherits = FALSE))
    .setupMethodsTables(fdef)
  sig <- .matchSigLength(sig, fdef, fenv, TRUE)
  label <- .sigLabel(sig)
  isCurrent <- exists(label, envir = table, inherits = FALSE)
  if(is.null(def)) { # remove the method (convention for setMethod)
      if(isCurrent)
          remove(list = label, envir = table)
  }
  else {
      dupl <- .duplicateClassesExist()
      ## ensure that a valid object is assigned:  if duplicate classes
      ## exist, may need a table by package label; else, make sure
      ## the target and defined slots are complete
      ## IF we believed all methods up to date, the call could be conditional
      ##    if(dupl || isCurrent)
      def <- .methodPackageSlots(def, label, table, dupl, isCurrent)
      assign(label, def, envir = table)
  }
}

## check for duplicate classes and embed method in an environment if so
.methodPackageSlots <- function(def, ...) def

## the real version
..methodPackageSlots <- function(def, label, table, duplicatesExist, isCurrent) {
    sig <- def@target
    dups <- FALSE
    if(duplicatesExist) {
        def <- .fixPackageSlot(def, sig)
        for(cl in sig) {
            if(exists(cl, envir = .classTable, inherits = FALSE) && is.list(get(cl, envir = .classTable))) {
                dups <- TRUE
                break
            }
        }
        if(isCurrent) { # check that this is overwriting identical signature
            current <- get(label, envir = table)
            dups <- dups || !identical(current@target, sig)
        }
        if(dups) {
            if(isCurrent) {
                if(is(current, "MethodDefinition")) {
                    pkg <- attr(current@target, "package")
                    if(length(pkg) == 0)
                        current <- .fixPackageSlot(current, current@target)
                    env <- new.env()
                    ## zero-length seen 2011-07-29
                    var <- .pkgMethodLabel(current)
                    if(nzchar(var)) assign(var, current, envir = env)
                }
                else if(is.environment(current))
                    env <- current
                else
                    stop(
                         gettextf("bad method object stored in method table, class %s",
                                  dQuote(class(current))),
                         domain = NA)
            }
            else
                env <- new.env()
            assign(.pkgMethodLabel(def), def, envir = env)
            env
        }
        else # no change
            def
    }
    else # no duplicate classes
        def
}

.fixPackageSlot <- function(def, sig) {
    ## check the pkg slot
    pkgs <- attr(sig, "package")
    if(is.null(pkgs))
        pkgs <- character(length(sig))
    fixme <- !nzchar(pkgs)
    if(any(fixme)) {
        for(i in seq_along(pkgs)[fixme])
            pkgs[[i]] <- getClass(sig[[i]], .Force = TRUE)@package
        attr(sig, "package") <- pkgs
        def@target <- sig
        ## check the defined signature as well
        sig <- def@defined
        pkgs <- attr(sig, "package")
        if(is.null(pkgs))
            pkgs <- character(length(sig))
        fixme <- !nzchar(pkgs)
        if(any(fixme)) {
            for(i in seq_along(pkgs)[fixme])
                pkgs[[i]] <- getClass(sig[[i]], .Force = TRUE)@package
            attr(sig, "package") <- pkgs
            def@defined <- sig
        }
    }
    def
}

.okMethodLabel <- function(method) {
    if(is(method, "MethodDefinition")) {
        pkgs <- packageSlot(method@target)
        length(pkgs) > 0 && all(nzchar(pkgs))
    }
    else
        TRUE # primitive or environment
}


.pkgMethodLabel <- function(method) {
    sig <- method@target
    pkgs <- packageSlot(sig)
    if( (length(pkgs) < length(as.character(sig))) || any(!nzchar(pkgs)))
        stop("package slot missing from signature for generic ",
             sQuote(method@generic), "\n",
             "and classes ", paste(sig, collapse = ", "), "\n",
             "cannot use with duplicate class names (the package may need to be re-installed)",
             call. = FALSE, domain = NA)
    paste(pkgs, collapse = "#")
}

.resetTable <- function(table, n, signames) {
    ## protect this computation, in case it's resetting
    ## something used in the computation
    primMethods <- .allowPrimitiveMethods(FALSE)
    on.exit(.allowPrimitiveMethods(primMethods))
    ## after updating a methods table, the maximum no. of arguments in
    ## the signature increased to n.  Reassign any objects whose label
    ## does not match n classes from the defined slot
    anyLabel <- rep("ANY", n)
    anyPkg <- rep("methods", n)
    seqN <- 1L:n
    labels <- names(table)
    for(what in labels) {
        method <- get(what, envir = table)
        if(is.primitive(method)) # stored as default ?
            newSig <- anyLabel
        else if(is(method, "MethodDefinition"))
            newSig <- method@defined
        else if(is(method, "environment")) {
            newSig <- strsplit(what, "#", fixed = TRUE)[[1]]
            .resetTable(method, n, signames)
        }
        else
            stop(gettextf("invalid object in methods table (%s), expected a method, got an object of class %s",
                          sQuote(what),
                          dQuote(class(method))),
                 domain = NA)

        if(is(method, "MethodDefinition")) {
            pkgs <- packageSlot(newSig)
            newSig <- as(ifelse(seqN > length(newSig), anyLabel, newSig), "signature")
            newSig@names <- signames
            newSig@package <-  ifelse(seqN > length(pkgs), anyPkg, pkgs)
            method@defined <- method@target <- newSig
            newLabel <- .sigLabel(newSig)
        }
        else
            newLabel <- .sigLabel(ifelse(seqN > length(newSig), anyLabel, newSig))
        remove(list=what, envir = table)
        assign(newLabel, method, envir = table)
    }
    NULL
}

### the tag associated with a method signature.
### Should perhaps use the same C code as dispatch, for consistency,
### however, that code breaks out early in the collapse loop if no match.
### This code is not used for quick matching, so efficiency less critical.
.sigLabel <- function(sig)
  paste(sig, collapse = "#")

.fillSignatures <- function(sigs, n) {
    siglens <- lengths(strsplit(sigs, "#", fixed=TRUE))
    short <- n > siglens
    suffix <- vapply(n - siglens[short],
                     function(ni) paste(rep("ANY", ni), collapse="#"),
                     character(1L))
    sigs[short] <- paste(sigs[short], suffix, sep="#")
    sigs
}

## workhorse of selectMethod() [ -> ../Methods.R ] "
.findInheritedMethods <-
    function(classes, fdef, mtable = NULL,
             table = get(".MTable", envir = environment(fdef)),
             excluded = NULL, useInherited,
             simpleOnly = .simpleInheritanceGeneric(fdef), verbose = FALSE,
             doCache = is.environment(mtable),
             where = environment(fdef))
{
    ## to avoid infinite recursion, and somewhat for speed, turn off S4 methods for primitives
    if(.allowPrimitiveMethods(FALSE)) # if it was true, revert on exit
        on.exit(.allowPrimitiveMethods(TRUE))
    ## classes is a list of the class(x) for each arg in generic
    ## signature, with "missing" for missing args
    if(!is.environment(table)) {
        if(is(fdef, "standardGeneric"))
          stop(gettextf("invalid or unset methods table in generic function %s", sQuote(fdef@generic)), domain = NA)
        else
          stop("trying to find a methods table in a non-generic function")
    }
    hasGroup <- length(fdef@group) > 0L
    if(hasGroup)
      groupGenerics <- .getAllGroups(list(fdef))
    doExcluded <- length(excluded) > 0L
    if(verbose) {
	plist <- function(x) paste(x, collapse = ", ")
	cat(" .findInheritedMethods(): (hasGroup, doCache, doExcluded)= (",
	    plist(c("f","T")[1+c(hasGroup, doCache, doExcluded)]), ")\n",
	    if(hasGroup) paste0(" Group generics: ",
				plist(vapply(groupGenerics, slot,
					     character(1), "generic")), "\n"),
	    sep='')
    }
    nargs <- length(classes)
    if(!missing(useInherited) && length(useInherited) < nargs)
      useInherited <- rep(useInherited, length.out = nargs)
    if(hasGroup && !doExcluded) {
        ## first try for an exact match in a group generic
        ## If this matches &  is cached, it then will be treated as a non-inherited method
        ## so no further calls here should occur.
        ##
        ## doExcluded is the findNextMethod case; we don't regard group methods as
        ## inherited in the nextMethod sense, since they have the same signature
        label <- .sigLabel(classes)
        direct <- .getGroupMethods(label, groupGenerics, FALSE)
	if(length(direct)) {
	    if(doCache)
		assign(label, direct[[1L]], envir = mtable)
	    return(direct)
	}
        ## else, continue because we may want all defined methods
    }
    cl1 <- classes[[1L]]
    def <- getClass(cl1, where = where, .Force = TRUE)
    labels <-
      if(missing(useInherited) || useInherited[[1L]])
          c(cl1, .eligibleSuperClasses(def@contains, simpleOnly), "ANY")
      else cl1
    supersList <- list(labels)
    classDefs <- vector("list", nargs)
    classDefs[[1L]] <- def
    if(nargs > 1) { ## further arguments
        for(i in 2:nargs) {
            cc <- classDefs[[i]] <- getClass(classes[[i]], where = where,
                                             .Force = TRUE)
            allLabels <- if(missing(useInherited) || useInherited[[i]])
                c(cc@className, .eligibleSuperClasses(cc@contains, simpleOnly),
                  "ANY")
            else cc@className
            labels <- outerLabels(labels, allLabels)
            supersList <- c(supersList, list(allLabels))
        }
    }
    labels <- labels[-1L] # drop exact match
    labels <- unique(labels)# only needed while contains slot can have duplicates(!)
    if(verbose) {
	cat(" .fI> length(unique(method labels)) = ", length(labels))
	if(verbose >= 2) { cat(";  labels = \n") ; print(labels) }
    }
    allMethods <- names(table)
    m <- match(labels, .fillSignatures(allMethods, length(classes)))
    found <- !is.na(m)
    methods <- mget(allMethods[m[found]], table)
    if(verbose) cat(" >> found: ", length(methods), "\n")
    if(hasGroup) {
        ##  add the  group methods recursively found but each time
        ## only those not already included in found.
        groupmethods <- .getGroupMethods(labels, groupGenerics, found)
        fromGroup <- c(rep(FALSE, length(methods)),
                       rep(TRUE,  length(groupmethods)))
        if(verbose) cat(" .fI> #{additional group methods}:",
                        length(groupmethods),"\n")
        methods <- c(methods, groupmethods)
    }
    else
      fromGroup <- rep(FALSE, length(methods))
    ## resolve any duplicate-class ambiguities
    if(.duplicateClassesExist()) {
        found <- integer()
        nm <- names(methods)
        for(i in seq_along(methods)) {
            m <- methods[[i]]
            if(is.environment(m)) {
                methods[[i]] <- .checkDuplicateMethodClasses(classDefs, m, nm[[i]])
                found <- c(found, i)
            }
        }
        if(length(found))
            methods <- unlist(methods, recursive = FALSE)
        if(!is.list(methods)) # reduced to a single method?
            methods <- list(methods)
    }
    if(doExcluded)
      methods <- methods[is.na(match(names(methods), as.character(excluded)))]
    ## remove default (ANY,..,ANY) if its not the only method:
    if(length(methods) > 1L) {
        defaultLabel <- paste(rep.int("ANY", nargs), collapse = "#")
        i <- match(defaultLabel, names(methods), 0L)
        if(i > 0L) {
            methods <- methods[-i]
            fromGroup <- fromGroup[-i]
        }
    }
    if(length(methods) > 1L) {
        if(verbose) cat(" .fI> length(methods) = ", length(methods),
                        " --> ambiguity\n")
        ## have ambiguity to resolve
        select <- .getBestMethods(methods, supersList, fromGroup, verbose=verbose)
        ##         --------------
        if(length(select) > 1L) {
            if(verbose) cat(" .fI> found", length(select)," best methods\n")

##            target <- .sigLabel(classes)
            condAction <- getOption("ambiguousMethodSelection")
            if(is.null(condAction))
              condAction <- .ambiguousMethodMessage
            else if(!is.function(condAction))
              stop(gettextf("the \"ambiguousMethodSelection\" option should be a function to be called as the condition action; got an object of class %s",
                            dQuote(class(condAction))),
                   domain = NA)

            select <- withCallingHandlers(
                                          .disambiguateMethods(classes, select, fdef@generic,
                                                               methods, supersList, fromGroup,
                                                               classDefs, verbose),
                                          ambiguousMethodSelection=condAction)
        }
        methods <- methods[select]
    }
    if(simpleOnly && length(methods) == 0L) {
	## Seems to be *unused* [below, 'simpleOnly' argument was missing for years!]
	methods <- Recall(classes, fdef, mtable, table, excluded, useInherited,
			  simpleOnly, verbose, FALSE)
        if(length(methods) > 0L)
          message(gettextf("No simply inherited methods found for function %s; using non-simple method",
                           sQuote(fdef@generic)),
                  domain = NA)
    }
    if(length(methods)) {
        tlabel <- .sigLabel(classes)
        m <- methods[[1L]]
        if(is(m, "MethodDefinition"))  { # else, a primitive
            m@target <- .newSignature(classes, fdef@signature)
            ## if any of the inheritance is not simple, must insert coerce's in method body
            coerce <- .inheritedArgsExpression(m@target, m@defined, body(m))
            if(!is.null(coerce))
              body(m) <- coerce
            methods[[1L]] <- m
        }
	if(doCache) {
	    if(verbose) cat(" .fI> caching newly found methods ..\n")
	    assign(tlabel, m, envir = mtable)
	}
    }
    methods
}

.checkDuplicateMethodClasses <- function(classDefs, env, label){
    supers <- strsplit(label, "#", TRUE)[[1]]
    sigs <- sort(names(env))
    plabels <- strsplit(sigs, "#", TRUE)
    hasSubclass <- vapply(plabels, .hasThisSubclass, logical(1L),
                          classDefs=classDefs, supers=supers)
    mget(sigs[hasSubclass], env)
}

.hasThisSubclass <- function(classDefs, supers, plabel) {
    for(i in seq_along(plabel)) {
        pkg <- classDefs[[i]]@package
        cl <- classDefs[[i]]@className
        si <- supers[[i]]
        pki <- plabel[[i]]
        if(identical(si, "ANY") ||
           (identical(cl, si) && identical(pkg, pki)))
            next
        cli <- getClassDef(si, package = pki)
        if(is.null(cli)) return(FALSE)
        sub <- cli@subclasses[[cl]]
        if(is.null(sub) || !identical(pkg, sub@package))
            return(FALSE)
    }
    TRUE
}

.ambiguousMethodMessage <- function(cond) {
  selected <- attr(cond, "selected")
  if(is.null(selected)) {# not properly set up, so just use the message
    message(cond$message)
  }
  else {
    possible <- attr(cond, "candidates")
    message(gettextf("Note: method with signature %s chosen for function %s,\n target signature %s.\n %s would also be valid",
                     sQuote(selected),
                     sQuote(attr(cond, "generic")),
                     sQuote(attr(cond, "target")),
		     paste0('"', possible[is.na(match(possible, selected))], '"',
			    collapse=", ")),
            domain = NA)
  }
}

.simpleInheritanceGeneric <- function(fdef) {
    identical(attr(fdef@signature, "simpleOnly"), TRUE)
}

.eligibleSuperClasses <- function(contains, simpleOnly) {
    what <- names(contains)
    if(!length(what))
      what
    else {
	eligible <-
	    vapply(contains,
		   if(simpleOnly)
		   function(x) (is.logical(x) && x) || x@simple
		   else # eliminate conditional inheritance
		   function(x) (is.logical(x) && x) || x@simple || isTRUE(body(x@test)), NA)
	what[eligible]
    }
}

.newSignature <- function(classes, names) {
  ## a simple version to deal with bootstrapping stage, used in new() etc
    n <- min(length(classes), length(names))
  i <- seq_len(n)
    ## a corresponding set of package names
    ## <FIXME> There should be a "<unknown>" package name instead of "methods"
    ## but this requires a way to deal with that generally </FIXME>
    pkgs <- lapply(classes[i], packageSlot)
    pkgs[vapply(pkgs, is.null, logical(1L))] <- "methods"

  ## Simplified version ...
  .asS4(structure(as.character(classes)[i],
            class = .signatureClassName,
            names = as.character(names)[i],
            package = as.character(pkgs) ))
 }

.findNextFromTable <- function(method, f, optional, envir, prev = character())
{
    fdef <- getGeneric(f, where = envir)
    env <- environment(fdef)
##    target <- method@target
    n <- get(".SigLength", envir = env)
    defined <- method@defined
    m <- length(defined)
    if(m > n)
        length(defined) <- n
  else if(n > m)
      ## will only really need this to be a signature when the elements
      ## have package attribute--see .sigLabel
      defined <-  new("signature", fdef, c(defined@.Data, rep("ANY", n-m)))
    excluded <- c(prev, .sigLabel(defined))
    allTable <- .getMethodsTable(fdef, inherited = TRUE)
    methods <- .findInheritedMethods(defined, fdef, mtable = NULL,
                                     table = allTable,
                                     excluded = excluded,
                                     where = envir)
    if(length(methods) == 0L) # use default method, maybe recursively.
        methods <- list(finalDefaultMethod(fdef@default)) #todo: put a label on it?
    if(length(methods) > 1L)
        warning(sprintf(ngettext(length(methods),
                                 "found %d equally good next method",
                                 "found %d equally good next methods"),
                        length(methods)),
                domain = NA)
    ## excluded slot is a list, but with methods tables, elements are just labels
    new("MethodWithNext", method, nextMethod = methods[[1L]],
        excluded = as.list(excluded))
}


  ## get the classes of the args

.InheritForDispatch <- function(classes, fdef, mtable) {
  methods <- .findInheritedMethods(classes, fdef, mtable)
  if(length(methods) == 1L)
    return(methods[[1L]]) # the method
  else if(length(methods) == 0L) {
    cnames <- paste0(fdef@signature[seq_along(classes)], ' = "',
                     vapply(classes, as.character, ""), "\"",
		     collapse = ", ")
    stop(gettextf("unable to find an inherited method for function %s for signature %s",
                  sQuote(fdef@generic),
                  sQuote(cnames)),
         call. = FALSE,
         domain = NA)
  }
  else
    stop("Internal error in finding inherited methods; didn't return a unique method", domain = NA)
}

.findMethodForFdef <- function(signature, table, fdef = NULL) {
    value <- .findMethodInTable(signature, table, fdef)
    if(is.null(value) && is(fdef, "genericFunction")) { # try without expanding signature
        fullSig <- .matchSigLength(signature, fdef, environment(fdef), FALSE)
        if(!identical(fullSig, signature))
            value <- .findMethodInTable(signature, table, fdef, FALSE)
    }
    value
}

.findMethodInTable <- function(signature, table, fdef = NULL , expdSig = TRUE)
{
    if(is(fdef, "genericFunction") && expdSig)
        signature <- .matchSigLength(signature, fdef, environment(fdef), FALSE)
    label <- .sigLabel(signature)
##     allMethods <- objects(table, all.names=TRUE)
##     if(match(label, allMethods, nomatch = 0L))
    if(!is.null(value <- table[[label]])) {
        if(is.environment(value)) {
            pkgs <- names(value)
            if(length(pkgs) == 1)
                value <- value[[pkgs]]
            else if(length(pkgs) == 0)
                value <- NULL
            ## else, return the environment indicating multiple possibilities
        }
        value
    } # else, NULL
}

## inheritance distances:  0 for the class, 1 for immediate contains, 2 for other contains
##    and 3 for ANY
.inhDistances <- function(classDef) {
  contains <- classDef@contains
  allNames <-  unique(names(contains)) # bug allows duplicates in contains
  dist <- rep(2, length(allNames))
  for(i in seq_along(dist)) {
    ci <- contains[[i]]
    dist[[i]] <- ci@distance
  }
  dist <- c(0, dist, NA)
  names(dist) <- c(classDef@className, allNames, "ANY")
  dist
}

.leastMethodDistance <- function(methods, supersList, classDefs, fromGroup, verbose = FALSE) {
    n <- length(methods)
    dist <- rep(0, n)
    nArg <- length(classDefs)
    defClasses <- matrix("ANY", nArg, n)
    for(j in 1L:n) {
	cl <- methods[[j]]@defined@.Data
	defClasses[seq_along(cl), j] <- cl
    }
    containsDist <- lapply(classDefs, .inhDistances)
    maxDist <- max(unlist(containsDist), na.rm = TRUE) + 1
    if(verbose) { cat("** individual arguments' distances:\n"); print(containsDist) }
    ## add up the inheritance distances for each argument (row of defClasses)
    for(i in 1L:nArg) {
	ihi <- containsDist[[i]]
	ihi[is.na(ihi)] <- maxDist
	cli <- defClasses[i,]
	dist <- dist + ihi[match(cli, names(ihi))]
    }
    ## These should be integers, so we do not need to worry about a decimal point
    if(verbose) cat("** final methods' distances: (",
		    paste(formatC(dist), collapse= ", "), ")\n", sep='')
    best <- dist == min(dist)
    ## of the least distance methods, choose direct, rather than group
    ## methods, unless all the best methods are from group generics
    if(any(fromGroup[best]) && !all(fromGroup[best]))
	best <- best & !fromGroup
    (1:n)[best]
}

## currently called exactly once from .findInheritedMethods() :
.getBestMethods <- function(methods, supersList, fromGroup, verbose = FALSE) {
    n <- length(methods)      ## >= 2
    nArg <- length(supersList)## >= 1
    sigs <- matrix("ANY", nArg, n)
    for(i in 1:n) {
      sig <- methods[[i]]@defined
      if(length(sig) < nArg) { # is this still possible? --> show 'verbose'
	if(verbose) cat(sprintf(" .. method %d: length(sig) = %d < nArg = %d\n",
				i, length(sig), nArg))
	sigs[seq_along(sig), i] <- sig
      }
      else
        sigs[,i] <- sig
    }
    if(nArg < 2) { # the easy case
      return(which.min(match(sigs[1L,], supersList[[1L]])))
    }
    ## else  nArg >= 2
    pos <- matrix(0L, nArg, n)
    for(i in 1:nArg) {
        pos[i,] <- match(sigs[i,], supersList[[i]])
    }
    valid <- colSums(is.na(pos)) == 0L
    best <- valid
    dominated <- !valid
    ## pairwise comparison of columns of pos.  Any way to vectorize?
    seqn <- seq_len(n)
    for(i in seqn[valid]) {
      for(j in seqn[-i][valid[-i]]) {
        diffs <- pos[,j] - pos[,i]
	if(any(diffs < 0))  { best[i] <- FALSE; if(dominated[i]) break }
	if(all(diffs <= 0)) { dominated[i] <- TRUE; if(!best[i]) break }
      }
    }
    if(verbose)
	cat(if(any(best)) paste(" have best ones",
				paste(format(seqn[best]),collapse=","))
	    else if(any(dominated)) paste(" can eliminate dominated ones,",
				    paste(format(seqn[dominated]),collapse=",")),
	    "\n")
    ## a best method is as early in the superclasses as any other on all arguments
    ## Because the signatures are not duplicated, there can be at most one.
    if(any(best))
      seqn[best]
    ## eliminate those methods dominated by another
    else
      seqn[!dominated]
}

## currently called exactly once from .findInheritedMethods() :
.disambiguateMethods <- function(target, which, generic, methods, supersList,
                                 fromGroup, classDefs, verbose)
{
  ## save full set of possibilities for condition object
  candidates <- methods[which]
  note <- character()
  ## choose based on total generational distance
  which2 <- .leastMethodDistance(candidates, supersList, classDefs,
                                 fromGroup[which])
  if(length(which2) < length(which)) {
    note <- c(sprintf(ngettext(which2,
                               "Selecting %d method of minimum distance",
                               "Selecting %d methods of minimum distance"),
                      which2))
    which <- which[which2]
  }
  ## if some are group methods, eliminate those
  if(length(which) > 1 && any(fromGroup[which]) && !all(fromGroup[which])) {
    which <- which[!fromGroup]
    note <- c(note,  sprintf(ngettext(length(which),
                                      "Selecting %d non-group method",
                                      "Selecting %d non-group methods"),
                             length(which)))
  }
  ## prefer partially direct methods
  if(length(which) > 1) {
    direct <- vapply(methods[which], function(x, target)
                     (is(x, "MethodDefinition") && any(target == x@defined)),
		     NA, target = target)
    if(any(direct) && !all(direct)) {
      which <- which[direct]
      note <- c(note, sprintf(ngettext(length(which),
                                       "Selecting %d partially exact-matching method",
                                       "Selecting %d partially exact-matching methods"),
                              length(which)))
    }
  }
  which <- which[[1L]]
  if(identical(as.character(generic), "coerce"))
      return(which) # as() computations not currently consistent w. selection (R 2.15.2)
  selected <- names(methods)[[which]]
  ## FIXME (?): This is not shown to the user
  msg <- sprintf(ngettext(length(candidates),
                          "Choosing method %s from %d ambiguous possibility",
                          "Choosing method %s from %d ambiguous possibilities"),
                 sQuote(selected), length(candidates))
  condObject <- simpleCondition(msg)
  ## would be nice to use an S4 class eventually
  class(condObject) <- c("ambiguousMethodSelection", class(condObject))
  attributes(condObject) <-
      c(attributes(condObject),
	list("candidates" = names(candidates),
	     "target"	  = .sigLabel(target),
	     "selected"	  = selected,
	     "generic"	  = generic,
	     "notes" = if(length(note)) paste(note, collapse ="; ") else ""))
  if(verbose) cat("   .disambiguateM*(): notes =\n\t",
		  attr(condObject, "notes"), "\n")
  signalCondition(condObject)
  which
}

# add objects to the generic function's environment that allow
# table-based dispatch of methods
.setupMethodsTables <- function(generic,
		initialize = !exists(".MTable", envir = env, inherits = FALSE))
{
    env <- environment(generic)
    if(initialize || !exists(".SigLength", envir = env, inherits = FALSE)) {
        nsig <- 1
        ## check that groups of generics agree on .SigLength; otherwise
        ## labels won't match
        for(gp in generic@group) {
            gpDef <- getGeneric(gp)
            if(is(gpDef, "genericFunction")) {
                .getMethodsTable(gpDef) # force initialization
                nsig <- max(nsig, get(".SigLength", envir = environment(gpDef)))
            }
        }
        assign(".SigLength", nsig, envir = env)
    }
    argSyms <- lapply(generic@signature, as.name)
    assign(".SigArgs", argSyms, envir = env)
    if(initialize) {
        mtable <- generic@default # from 2.11.0: method, primitive or NULL, not MethodsList
        mtable <- .mtableAddToTable(generic, mtable) # by default, adds to an empty table
        assign(".MTable", mtable, envir = env)
    }
    else ## the current .MTable
        mtable <- getMethodsForDispatch(generic)
    .resetInheritedMethods(env, mtable)
    if(is(generic, "groupGenericFunction")) {
        for(gp in generic@groupMembers) {
            gpDef <- getGeneric(gp)
            if(is(gpDef, "genericFunction"))
                .getMethodsTable(gpDef) # force initialization w. group methods
        }
    }
    NULL
}

.updateMethodsInTable <- function(generic, where, attach) {
  fenv <- environment(generic)
  reset <- identical(attach, "reset")
  if(is.null(mtable <- fenv$.MTable)) {
      .setupMethodsTables(generic)
      mtable <- get(".MTable", envir = fenv)
  }
  if(!reset) {
    env <- as.environment(where)
    tname <- .TableMetaName(generic@generic, generic@package)
    if(!is.null(tt <- env[[tname]])) {
      .mergeMethodsTable(generic, mtable, tt, attach)
    }
    ## else used to warn, but the generic may be implicitly required
    ## by class inheritance, without any explicit methods in this package
  }
  if(length(generic@group)) {
      groups <- as.list(generic@group)
      generics <- vector("list", length(groups))
      for(i in seq_along(groups))
        generics[[i]] <- getGeneric(groups[[i]])
    .checkGroupSigLength(groups, generics)
  }
  if(is(generic, "groupGenericFunction")) {
      .checkGroupSigLength(list(generic@generic), list(generic))
      for(g in getGroupMembers(generic))
          .updateMethodsInTable(getGeneric(g), where, attach)
  }
  .resetInheritedMethods(fenv, mtable)
  mtable
}

.resetInheritedMethods <- function(fenv, mtable) {
    allObjects <- character()
    direct <- names(mtable)
    if(!is.null(allTable <- fenv$.AllMTable)) {
        ## remove all inherited methods.  Note that code (e.g. setMethod) that asigns
        ## a new method to mtable is responsible for copying it to allTable as well.
        allObjects <- names(allTable)
        remove(list = setdiff(allObjects, direct), envir = allTable)
    }
    else {
        allTable <- new.env(TRUE, fenv)
        assign(".AllMTable", allTable, envir = fenv)
    }
    ## check for missing direct objects; usually a non-existent AllMTable?
    if(anyNA(match(direct, allObjects))) {
        list2env(as.list(mtable, all.names=TRUE), allTable)
    }
    for (d in direct) {
        m <- allTable[[d]]
        if (is(m, "MethodWithNext"))
            allTable[[d]] <- as(m, "MethodDefinition")
    }
    NULL
}

## In the following, consider separate "compute" and "print" functions/methods:
## Wish: alternative to 'classes' allow  "wild-card signature", e.g.,
##       showMethods("coerce", signature = c("dgeMatrix", "*"))
.showMethodsTable <- function(generic, includeDefs = FALSE, inherited = FALSE,
                              classes = NULL, showEmpty = TRUE, printTo = stdout())
{
    cf <- function(...) cat(file = printTo, sep = "", ...)
    sigString <- function(sig)
	paste0(names(sig), "=\"", as.character(sig), "\"", collapse = ", ")
##    qs <- function(what) paste0('"', what, '"', collapse = ", ")
    doFun <- function(func, pkg) cf("Function: ", func, " (package ", pkg, ")\n")
    env <- environment(generic)
##    signature <- generic@signature
    table <- get(if(inherited) ".AllMTable" else ".MTable", envir = env)
    f <- generic@generic
    p <- packageSlot(f)
    if(is.null(p)) p <- "base"
    deflt <- new("signature", generic, "ANY")
    labels <- sort(names(table))
    if(!is.null(classes) && length(labels) > 0L) {
	sigL <- strsplit(labels, split = "#")
	keep <- !vapply(sigL, function(x, y) all(is.na(match(x, y))), NA, y=classes)
	labels <- labels[keep]
    }
    if(length(labels) == 0L) {
	if(showEmpty) {
	    doFun(f,p)
	    cf("<No methods>\n\n")
	}
	return(invisible())
    }
    ## else: non-empty methods list
    doFun(f,p)
    for(m in mget(labels, table)) {
        pkgs <- NULL
	if(is.environment(m)) {  ## duplicate class case -- compare .findMethodInTable()
            pkgs <- names(m)
            m <- m[[pkgs[1L]]]
        }
	if( is(m, "MethodDefinition")) {
	    t <- m@target
	    if(length(t) == 0L)
		t <- deflt
	    d <- m@defined
	    if(length(d) == 0L)
		d <- deflt
	    cf(sigString(t), "\n")
	    if(!identical(t, d))
		cf("    (inherited from: ", sigString(d), ")\n")
            if(!.identC(m@generic, f) && length(m@generic) == 1L &&
               nzchar(m@generic))
		cf("    (definition from function \"", m@generic, "\")\n")
            if(length(pkgs) > 1)
                cf("  (", length(pkgs), " methods defined for this signature, with different packages)\n")
	}
	if(includeDefs && is.function(m)) {
	    if(is(m, "MethodDefinition"))
		m <- m@.Data
	    cat(deparse(m), sep="\n", "\n", file = printTo)
	}
    }
    cat("\n", file = printTo)
}

## temporary switch for tables (not used anywhere; for debugging)
useMTable <- function(onOff = NA)
  .Call(C_R_set_method_dispatch, as.logical(onOff))

## get all the group generic functions, in breadth-first order since
## direct group inheritance is closer than indirect (all existing
## groups are mutually exclusive, but multiple group membership is
## allowed)
.getAllGroups <- function(funs) {
  start <- length(funs)
  for(i in seq_along(funs)) {
    groups <- funs[[i]]@group
    funs <- c(funs, lapply(groups,
                           function(what) {
                             f <- getGeneric(what)
                             if(!is.function(f))
                               stop("failed to find expected group generic function: ",
                                    what)
                             f
                           }))
    }
    ## now the next generations recursively
    if(length(funs) > start) {
      nmore <- length(funs) - start
      more <- Recall(funs[(start+1):length(funs)])
      ## did we add any groups?
      if(length(more) > nmore)
        funs <- c(funs, more[(nmore+1):length(more)])
    }
    funs
}

.getGroupMethods <- function(labels, generics, found) {
  methods <- list()
  for(i in seq_along(generics)) {
    gen <- generics[[i]]
    if(!is(gen,"genericFunction"))
      stop(gettextf("invalid group generic function in search for inherited method (class %s)",
                    dQuote(class(gen))),
           domain = NA)
    table <- .getMethodsTable(gen)
    allMethods <- sort(names(table))
    ## TODO:  possible for .SigLength to differ between group &
    ## members.  Requires expanding labels to max. length
    newFound <- rep(FALSE, length(found))
    newFound[!found] <- labels[!found] %in% allMethods
    found <- found | newFound
    methods[labels[newFound]] <- mget(labels[newFound], table)
  }
  methods
}

.getMethodsTable <- function(fdef, env = environment(fdef),
                             check = TRUE, inherited = FALSE)
{
    name <- if(inherited) ".AllMTable" else ".MTable"
    if(check) {
        get0(name, envir = env, inherits = FALSE) %||% {
            .setupMethodsTables(fdef, initialize = TRUE)
            get0(name, envir = env, inherits = FALSE) %||% stop("invalid methods table request") 
        }
    }
    else
        get(name, envir = env)
}

.getGenericSigLength <- function(fdef, env = environment(fdef), check = TRUE) {
    if(check && !exists(".SigLength", envir = env, inherits = FALSE))
      .setupMethodsTables(fdef)
    get(".SigLength", envir = env)
}



.checkGroupSigLength <- function(gnames, generics = lapply(gnames, getGeneric)) {
  funs <- gnames
  recall <- FALSE
  for(i in seq_along(gnames)) {
    what <- gnames[[i]]
    fdef <- generics[[i]]
    if(!is(fdef, "groupGenericFunction")) {
      warning(gettextf("trying to check signature length of group generic '%s', but it is not a group generic", what),
              domain = NA)
      next
    }
    if(length(fdef@group))  {# push up the check one level
      gnames[[i]] <- fdef@group
      generics[[i]] <- lapply(fdef@group, getGeneric)
      recall <- TRUE
      next
    }
    funs <- c(funs, getGroupMembers(fdef, TRUE, FALSE))
  }
  if(recall)
    return(Recall(unlist(gnames, FALSE), unlist(generics, FALSE)))
  funs <- unique(funs)
  fdefs <- lapply(funs, function(x) {
    if(is.character(x) && length(x) == 1L) getGeneric(x)
    else x})
  ## now compare the sig lengths
  sigs <- rep(0,length(funs))
  for(i in seq_along(sigs)) {
    what <- funs[[i]]
    fdef <- fdefs[[i]]
    if(is.null(fdef))
      next # getGroupMembers returns NULL if  member is not defined
    if(!is(fdef, "genericFunction"))
      warning(gettextf("trying to check signature length of generic '%s', but it is not a generic function: i = %d, funs = %s, gnames = %s",
                       what,  i, paste(unlist(funs), collapse = ", "),
                       paste(as.character(gnames), collapse = ", ")),
              domain = NA)
    else {
      ev <- environment(fdef)
      if(is.null(sigl <- ev$.SigLength)) {
	  .setupMethodsTables(fdef)
	  sigl <- get(".SigLength", envir = ev)
      }
      sigs[i] <- sigl
    }
  }
  n <- max(sigs)
    reset <- sigs < n & sigs > 0 # all the  sigs  for defined funs & less than max.
  if(any(reset)) {
    funs <- funs[reset]
    fdefs <- fdefs[reset]
    for(fdef in fdefs) {
        .resetSigLength(fdef, n)
    }
  }
  funs
}

## a simplified outer of paste
outerLabels <- function(labels, new) {
    ## WARNING: This code incorporates the definition of .sigLabel
    ## and so must change if that does (e.g. to include package)
    n <- length(labels)
    m <- length(new)
    paste(labels[rep.int(1L:n, rep.int(m,n))], new[rep.int(1L:m,n)], sep ="#")
}


.matchSigLength <- function(sig, fdef, fenv, reset = FALSE) {
  nargs <- .getGenericSigLength(fdef, fenv, TRUE)
  n <- length(sig)
  pkgs <- packageSlot(sig)
  if(n < nargs) {
      more <- nargs - n
      pkgs <- c(pkgs, rep("methods", more))
      sig <- c(as.character(sig), rep("ANY", more))
  }
  else if(n > nargs) { #reset table?
    if(all(sig[(nargs+1):n] == "ANY")) {
        length(sig) <- nargs
        if (!is.null(pkgs))
            length(pkgs) <- nargs
    } else {
      while(sig[[n]] == "ANY")
        n <- n-1
      if(reset)
        .resetSigLength(fdef, n)
      length(sig) <- n
      if (!is.null(pkgs))
          length(pkgs) <- n
    }
  }
  packageSlot(sig) <- pkgs
  sig
}

.resetSigLength <- function(fdef, n) {
    fenv <- environment(fdef)
    assign(".SigLength", n, envir = fenv)
    mtable <- .getMethodsTable(fdef, fenv, check = FALSE)
    signames <- fdef@signature
    length(signames) <- n
    .resetTable(mtable, n, signames)
    .resetInheritedMethods(fenv, mtable)
}

.TableMetaName <- function(name, package)
  methodsPackageMetaName("T", paste(name, package, sep=":"))

.TableMetaPrefix <- function()
    methodsPackageMetaName("T","")

# regexp for matching table names; semi-general but assumes the
# meta pattern starts with "." and has no other special characters
.TableMetaPattern <- function()
    paste0("^[.]",substring(methodsPackageMetaName("T",""),2))

.addToMetaTable <- function(fdef, signature, definition, where, nSig) {
  return()
}

## the real version
..addToMetaTable <- function(fdef, signature, definition, where,
                             nSig = .getGenericSigLength(fdef)) {
    ## TODO:  nSig should be a slot in the table
  tname <- .TableMetaName(fdef@generic, fdef@package)
  where <- as.environment(where)
  if(!is.null(table <- where[[tname]])) {
    if(length(signature) > nSig)
      .resetTable(table, length(signature), fdef@signature[seq_along(signature)])
  }
  else {
    table <- new.env(TRUE, environment(fdef))
    assign(tname, table, envir = where)
  }
  .cacheMethodInTable(fdef, signature, definition, table)
}

.removeMethodsMetaTable <- function(generic, where) {
    ## does not warn if none exists, on the theory that a generic may be created
    ## but no methods defined to create a table.  The use of implicitGeneric's is an example.
    tname <- .TableMetaName(generic@generic, generic@package)
    if(exists(tname, where, inherits = FALSE))
      rm(list=tname, pos = where)
}

.getGenericSigArgs <- function(fdef, env = environment(fdef), check = TRUE) {
    if(check && !exists(".SigLength", envir = env, inherits = FALSE))
      .setupMethodsTables(fdef)
    n <- get(".SigLength", envir = env)
    args <-  get(".SigArgs", envir = env)
    length(args) <- n
    args
}


## the most simple part of listFromMethods() below; not yet exported
tableNames <- function(generic, where, table) {
    fdef <- getGeneric(generic)
    if(missing(table))
	table <-
	    if(missing(where)) .getMethodsTable(fdef)
	    else get(.TableMetaName(fdef@generic, fdef@package),
                     envir = as.environment(where), inherits = FALSE)
    names(table)
}

listFromMethods <- function(generic, where, table) {
    fdef <- getGeneric(generic)
    if(missing(table))
	table <-
	    if(missing(where)) .getMethodsTable(fdef)
	    else get(.TableMetaName(fdef@generic, fdef@package),
		     envir = as.environment(where), inherits = FALSE)
    fev <- environment(fdef)
    nSigArgs <- .getGenericSigLength(fdef, fev)
    methods <- as.list(table, all.names=TRUE)
    names <- names(methods)
    if(nSigArgs > 1) {
        n <- length(names)
        sigs <- vector("list", n)
        namesCon <- textConnection(names)
        for(i in seq_len(n))
            sigs[[i]] <- scan(namesCon, "", sep ="#", nmax = nSigArgs, quiet=TRUE)
    }
    else
      sigs <- as.list(names)
    new("LinearMethodsList", classes=sigs, methods=methods,
        arguments = .getGenericSigArgs(fdef, fev), generic = fdef)
}

.makeMlist1 <- function(arg, objects, j = 1) {
    .MlistDefunct(".makeMlist1()")
    mnames <- character(length(objects))
    for(i in seq_along(objects)) {
        what <- objects[[i]]
        if(is.primitive(what))
          sig <- "ANY"
        else
          sig <- what@defined
        mnames[[i]] <- (if(length(sig) < j) "ANY" else sig[[j]])
    }
    names(objects) <- mnames
    new("MethodsList", argument = arg, methods = objects, allMethods = objects)
}

.makeMlist2 <- function(args, objects, j = 1) {
    ## make a list according to  argument j, convert these as needed
    .MlistDefunct(".makeMlist2()")
    mlists <- list()
    for(what in objects) {
        sig <- if(!is.primitive(what)) what@defined # else NULL
        if(length(sig) <= j)
            arg1 <- arg2 <- "ANY"
        else {
            arg1 <- sig[[j]]
            arg2 <- sig[[j+1]]
        }
        x <- list(what)
        el <- mlists[[arg1, exact = TRUE]]
        mlists[[arg1]] <- (if(is.null(el)) x else c(el, x))
    }
    jNext <- j+1
    if(jNext < length(args))
      for(i in seq_along(mlists))
          mlists[[i]] <- .makeMlist2(args, mlists[[i]], jNext)
    else {
        arg2 <- as.name(args[[jNext]])
        for(i in seq_along(mlists))
          mlists[[i]] <- .makeMlist1(arg2, mlists[[i]], jNext)
    }
    new("MethodsList", argument = as.name(args[[1L]]),
        methods = mlists, allMethods = mlists)
}

.makeMlistFromTable <- function(generic, where = NULL) {
    .MlistDefunct(".makeMlistFromTable()")
    if(is.null(where)) {
        what <- ".MTable"
        where <- environment(generic)
    }
    else {
        where <- as.environment(where)
        what <-  .TableMetaName(generic@generic, generic@package)
    }
    if(exists(what, envir = where, inherits= FALSE))
        table <- get(what, envir = where)
    else
        table <- new.env()
    value <- new("MethodsList", argument = as.name(generic@signature[[1]]))
    allNames <- sort(names(table))
    if(length(allNames) == 0L)
      return(value)
    argNames <- generic@signature
    ## USES THE PATTERN OF class#class#.... in the methods tables
    nargs <- nchar(unique(gsub("[^#]","", allNames)))+1
    if(length(nargs) > 1L) {
	warning("something weird:  inconsistent number of args in methods table strings:",
		paste(nargs,collapse = ", ")," (using the largest value)",
                domain = NA)
        nargs <- max(nargs)
    }
    length(argNames) <- nargs # the number of args used
    if(nargs == 1)
        .makeMlist1(as.name(argNames[[1L]]), mget(allNames, table))
    else
      .makeMlist2(argNames, mget(allNames, table))
 }

## assign a methods meta-data table, by default (and usually) a copy of the table
## from the generic function with the initial methods, if any.
.assignMethodsTableMetaData <- function(name, generic, where, table) {
    what <-  .TableMetaName(generic@generic, generic@package)
    if(missing(table))
	table <- .copyEnv(.getMethodsTable(generic))
    assign(what, table, envir = as.environment(where))
}

.getMethodsTableMetaData <-  function(generic, where, optional = FALSE) {
    what <- .TableMetaName(generic@generic, generic@package)
    get0(what, envir = where, inherits = FALSE) %||%
        if(optional)
            NULL
        else
            stop(gettextf("no methods table for generic %s from package %s in package %s",
                    sQuote(generic@generic),
                    sQuote(generic@package),
                    sQuote(getPackageName(where))),
                 domain = NA)
}

setPackageSlot <- function(x, value) {
    packageSlot(x) <- value
    x
}

.inheritedArgsExpression <- function(target, defined, body) {
    expr <- substitute({}, list(DUMMY = "")) # bug if you use quote({})--is overwritten!!
    args <- names(defined)
    for(i in seq_along(defined)) {
        ei <- extends(setPackageSlot(target [[i]], packageSlot(target)[[i]]),
                      setPackageSlot(defined[[i]], packageSlot(defined)),
                      fullInfo = TRUE)
        if(is(ei, "SClassExtension")  && !ei@simple)
          expr[[length(expr) + 1L]] <-
            substitute(ARG <- as(ARG, DEFINED, strict = FALSE),
                       list(ARG = as.name(args[[i]]),
                            DEFINED = as.character(defined[[i]])))
    }
    if(length(expr) > 1L) {
       expr[length(expr) + 1L] <- list(body) # body could be NULL!
       expr
   }
    else
        NULL
}

testInheritedMethods <- function(f, signatures, test = TRUE,  virtual = FALSE,
                                 groupMethods = TRUE,  where = .GlobalEnv)
{
  ## Function relevantClasses is defined here to set object .undefClasses
  ## in testInheritedMethods as a marker to warn about undefined subclasses
  .relevantClasses <- function(classes, excludeVirtual, where, doinheritance) {
    classDefs <- lapply(classes, getClassDef, where)
    undefs <- vapply(classDefs, is.null, NA)
    if(any(undefs)) {
      .undefClasses <<- unique(c(.undefClasses, classes[undefs]))
      classes <- classes[!undefs]
      classDefs <- classDefs[!undefs]
    }
    if(doinheritance) {
      allSubs <- lapply(classDefs,  function(what) names(what@subclasses))
      allSubs <- unique(unlist(allSubs))
      pattern <- sapply(allSubs, .matchSubsPattern, classes, excludeVirtual)
      ## exclude virtuals
      if(excludeVirtual) {
        excl <- nzchar(pattern)
        pattern <- pattern[excl]
        allSubs <- allSubs[excl]
      }
      if(length(allSubs)>0)
        allSubs <- sapply(split(allSubs, pattern), `[[`,1)
      else
        allSubs <- character()
    }
    else
      allSubs <- character()
    ## prepend the classes themselves, as appropriate
    iAny <- match( "ANY", classes, 0L)
    if(iAny > 0L) {
      classes[[iAny]] <- ".Other" # non-virtual placeholder for ANY
      classDefs[[iAny]] <- getClassDef(".Other")
    }
    if(excludeVirtual)
      classes <- classes[vapply(classDefs, function(def) isFALSE(def@virtual), NA)]
    unique(c(classes, allSubs))
  }
  ## end of .relevantClasses

  if(!is(f, "genericFunction"))
    f <- getGeneric(f)
  fname <- f@generic
  if(missing(signatures)) {
    mdefs <- findMethods(f)
    mnames <- names(mdefs)
    sigs <-  findMethodSignatures(methods = mdefs)
    if(groupMethods) {
      groups <- getGroup(f, recursive = TRUE)
      for(group in groups) {
        fg <- getGeneric(group)
        mg <- findMethods(fg)
        sigsg <- findMethodSignatures(methods = mg)
        newSigs <- is.na(match(names(mg), mnames))
        ## mdefs <- c(mdefs, mg[newSigs])
        sigs <- rbind(sigs, sigsg[newSigs,])
        mnames <- c(mnames, names(mg)[newSigs])
      }
    }
    if(length(sigs) == 0)
      return(new("MethodSelectionReport", generic = fname))
    ## possible selection of which args to include with inheritance
    ok <- if(fname %in% c("coerce", "coerce<-"))
	match(colnames(sigs), "from", 0L) > 0L else rep.int(TRUE, ncol(sigs))
    for(j in seq_len(ncol(sigs))) {
      classesj <- unique(sigs[,j])
      .undefClasses <- character()
      subclasses <- .relevantClasses(classesj, !virtual, where, ok[[j]])
      nj <- length(subclasses)
      ##       if(nj == 0) {  ##FIXME, wrong test
      ##         warning(gettextf("No eligible subclasses for argument '%s' found, so no contribution to analysis",
      ##                          colnames(sigs)[[j]]), domain  = NA)
      ##         next
      ##       }
      if(j > 1) {
        ## replicate all the previous elements of subclasses a la outer
        subclasses <- rep(subclasses, rep.int(ncomb, nj))
        ncomb <- ncomb * nj
        sigLabels <- paste(rep(sigLabels, times = nj), subclasses, sep = "#")
      }
      else {
        sigLabels <- subclasses
        ncomb <- nj
      }

      if(length(.undefClasses)) {
        warning(gettextf("undefined classes (%s) will be ignored for argument '%s'",
                         paste0('"',unique(.undefClasses),'"', collapse=", "),
                         colnames(sigs)[[j]]), domain = NA)
        .undefClasses <- character()
      }
    } ## loop on j
    ## now split the individual labels back into signatures
    signatures <- strsplit(sigLabels, "#", fixed = TRUE)
  } ## end of missing(signatures) case
  else if(is(signatures, "matrix") && typeof(signatures) == "character"
       && ncol(signatures) <= length(f@signature)) {
      ## turn signatures back into a list
      siglist <- vector("list", nrow(signatures))
      for(i in seq_len(nrow(signatures)))
        siglist[[i]] <- signatures[i,]
      signatures <- siglist
  }
  else stop("argument 'signatures' must be a character matrix whose rows are method signatures")
  ambig_target <- character()
  ambig_candidates <- list()
  ambig_selected <- character()
  ambig_note <- character()
  if(test) {
    ## define a handler that accumulates the attributes from the condition object
    warninghandler <- function(cond) {
      ambig_target <<- c(ambig_target, attr(cond, "target"))
      ambig_candidates <<- c(ambig_candidates, list(attr(cond, "candidates")))
      ambig_selected <<- c(ambig_selected, attr(cond, "selected"))
      ambig_note <<- c(ambig_note, attr(cond, "notes"))
    }
    ambigOpt <- options(ambiguousMethodSelection = warninghandler)
    on.exit(options(ambigOpt))
    doSelect <-  function(sig) {
      x <- selectMethod(f = f, sig, optional = TRUE)
      if(is(x, "MethodDefinition")) {
        nsig <- x@defined
        if(length(nsig) < length(sig))
          c(nsig, rep("ANY", length(sig) - length(nsig)))
        else
          nsig
      }
      else if(is.null(x))
        rep_len("<NONE>", length(sig))
      else # primitive
        rep_len("ANY", length(sig))
    }
    signatures <- lapply(signatures, doSelect)
  }
  signatures <- vapply(signatures, paste0, "", collapse = "#")
  names(signatures) <- sigLabels

  new("MethodSelectionReport", generic = fname, allSelections = signatures,
      target = ambig_target, selected = ambig_selected,
      candidates = ambig_candidates, note = ambig_note)
}

.matchSubsPattern <- function(what, matchto, excludeVirtual) {
  def <- getClass(what)
  if(excludeVirtual && def@virtual)
    return("")
  matches <- match(names(def@contains), matchto, 0L)
  matches <- matches[matches > 0L]
  paste(matches, collapse=".")
}
